package com.ymx.darling.datasource.routing;

import com.ymx.darling.datasource.routing.dataSourceConfig.ConfigType;
import com.ymx.darling.datasource.routing.dataSourceConfig.SeataMode;
import com.ymx.darling.datasource.routing.dialect.RoutingDialectAutoConfigurationUtil;
import com.p6spy.engine.spy.P6DataSource;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.rm.datasource.xa.DataSourceProxyXA;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.*;


@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(RoutingDataSourceProperties.class)
@AutoConfigureBefore(value = DataSourceAutoConfiguration.class)
@ConditionalOnProperty(prefix = Config.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
public class RoutingDataSourceAutoConfiguration implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor {

    private final Logger logger = LoggerFactory.getLogger(RoutingDataSourceAutoConfiguration.class);

    private final Map<Object, Object> routingDataSources = new HashMap<>();
    private ApplicationContext applicationContext;

    @Primary
    @Bean(name = "dataSource")
    DataSource dataSource(RoutingDataSourceProperties routingDataSourceProperties) {
        Object primaryDataSource;
        if (StringUtils.hasText(routingDataSourceProperties.getPrimary())) {
            primaryDataSource = routingDataSources.get(routingDataSourceProperties.getPrimary());
        } else {
            Optional<Map.Entry<Object, Object>> firstOptional = routingDataSources.entrySet().stream().findFirst();
            primaryDataSource = firstOptional.map(Map.Entry::getValue).orElse(null);
        }
        Objects.requireNonNull(primaryDataSource, "找不到主数据源");
        SpringRoutingDataSource routingDataSource = new SpringRoutingDataSource();
        routingDataSource.setTargetDataSources(routingDataSources);
        routingDataSource.setDefaultTargetDataSource(primaryDataSource);
        routingDataSource.setLenientFallback(!routingDataSourceProperties.getStrictMode());
        logger.info("多数据源 初始化完成！！！！！！！！！！！！");
        return routingDataSource;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        Binder binder = Binder.get(applicationContext.getEnvironment());
        RoutingDataSourceProperties routingDataSourceProperties = binder.bind(Config.PREFIX, Bindable.of(RoutingDataSourceProperties.class)).get();
        Map<String, List<DataSource>> groupDataSourceMap = new HashMap<>();
        final JdbcConfigDecryptor jdbcConfigDecryptor;
        if (routingDataSourceProperties.getJdbcConfigDecrypt()) {
            jdbcConfigDecryptor = applicationContext.getBean(JdbcConfigDecryptor.class);
        } else {
            jdbcConfigDecryptor = null;
        }

        // 配置多数据源的方言
        RoutingDialectAutoConfigurationUtil.registerDialect(routingDataSourceProperties);

        // 遍历从数据源配置
        routingDataSourceProperties.getRouting().forEach((dsKey, value) -> {
            ConfigType configType = value.getConfigType();
            if (value.getName() == null) {
                value.setName(dsKey);
            }

            if (routingDataSourceProperties.getJdbcConfigDecrypt()) {
                if (jdbcConfigDecryptor != null) {
                    value.setUrl(jdbcConfigDecryptor.jdbcUrlDecrypt(value.getUrl()));
                }
                if (jdbcConfigDecryptor != null) {
                    value.setUsername(jdbcConfigDecryptor.usernameDecrypt(value.getUsername()));
                }
                if (jdbcConfigDecryptor != null) {
                    value.setPassword(jdbcConfigDecryptor.passwordDecrypt(value.getPassword()));
                }
            }

            DataSource dataSource = value.initializeDataSourceBuilder().build();
            if (Objects.nonNull(configType)) {
                binder.bind(Config.ROUTING_PREFIX + "." + dsKey + "." + configType.getKey(), Bindable.ofInstance(dataSource));
            }

            if (routingDataSourceProperties.getP6spy()) {
                dataSource = new P6DataSource(dataSource);
                logger.info("启用 p6spy");
            }

            if (routingDataSourceProperties.getSeata()) {
                if (Objects.isNull(routingDataSourceProperties.getSeataMode()) || SeataMode.XA == routingDataSourceProperties.getSeataMode()) {
                    routingDataSourceProperties.setSeataMode(SeataMode.XA);
                    dataSource = new DataSourceProxyXA(dataSource);
                } else if (SeataMode.AT == routingDataSourceProperties.getSeataMode()) {
                    dataSource = new DataSourceProxy(dataSource);
                }
                logger.info("开启 seata 事务：{}", routingDataSourceProperties.getSeataMode());
            }

            if (dsKey.contains(Config.GROUP_SPLIT)) {
                int index = dsKey.lastIndexOf(Config.GROUP_SPLIT);
                String groupName = dsKey.substring(0, index);
                String no = dsKey.substring(index + 1);
                try {
                    Integer.parseInt(no);
                    groupDataSourceMap.computeIfAbsent(groupName, key -> new ArrayList<>()).add(dataSource);
                } catch (NumberFormatException e) {
                    //ignore
                }
            }

            routingDataSources.put(dsKey, dataSource);
            DataSource finalDataSource = dataSource;
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(DataSource.class, () -> finalDataSource).getBeanDefinition();
            beanDefinition.setPrimary(false);
            beanDefinitionRegistry.registerBeanDefinition(dsKey, beanDefinition);
        });

        groupDataSourceMap.forEach((key, value) -> {
            if (routingDataSources.containsKey(key)) {
                throw new RuntimeException("重复的ds：" + key);
            }
            routingDataSources.put(key, new GroupDataSource(value));
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }
}
